/*
 * Copyright (C) 2009 Leandro Aparecido <lehphyro@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.metalframework.persistence.repository.extent;

import java.util.*;

import org.metalframework.persistence.*;
import org.metalframework.persistence.engine.*;
import org.metalframework.persistence.repository.collection.*;
import org.metalframework.util.*;
import org.springframework.context.*;
import org.springframework.dao.*;

import com.google.code.liquidform.*;

/**
 * Set implementation of {@link org.metalframework.persistence.Extent}.
 * 
 * @param <T> Type of the entity of the extent.
 * 
 * @since 1.0
 * @see org.metalframework.persistence.Extent
 */
public class SetExtent<T> extends PersistentSet<T> implements Extent<T> {

	private static final long serialVersionUID = 3763895003029822261L;

	private ApplicationContext applicationContext;
	
	/**
	 * Creates a persistent set with the specified strategy.
	 * 
	 * @param strategy Strategy to interact with the repository.
	 */
	public SetExtent(PersistentCollectionStrategy<T> strategy, ApplicationContext applicationContext) {
		super(strategy);
		
		this.applicationContext = applicationContext;
	}

	@SuppressWarnings("unchecked")
	public <K> K addInstance(T entity) {
		return (K)getJapaEngine().persist(getStrategy().getEntityType(), entity);
	}

	public void set(T entity) {
		getJapaEngine().merge(entity);
	}

	public void set(Object id, T entity) {
		getJapaEngine().merge(getStrategy().getEntityType(), id, entity);
	}

	public T byId(Object id) {
		T entity = getJapaEngine().findById(getStrategy().getEntityType(), id);
		return ApplicationContextUtils.configure(entity, applicationContext);
	}

	public <E extends Throwable> T byId(Object id, Class<E> exception) throws E {
		T entity = byId(id);
		if (entity == null) {
			try {
				throw exception.newInstance();
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			} catch (InstantiationException e) {
				throw new RuntimeException(e);
			}
		}
		return ApplicationContextUtils.configure(entity, applicationContext);
	}

	public List<T> query(OrderByClause clause, Object... params) {
		return query(clause.toString(), params);
	}

	public List<T> query(SelectClause<T> clause, Object... params) {
		return query(clause.toString(), params);
	}

	public List<T> query(SubQuery<T> clause, Object... params) {
		return query(clause.toString(), params);
	}

	public List<T> query(String clause, Object... params) {
		List<T> queryResult = getJapaEngine().query(clause, params);
		List<T> result = new ArrayList<T>(queryResult.size());
		
		for (T entity : queryResult) {
			result.add(ApplicationContextUtils.configure(entity, applicationContext));
		}
		
		return result;
	}

	public T queryForSingleInstance(OrderByClause clause, Object... params) {
		return queryForSingleInstance(clause.toString(), params);
	}

	public <E extends Throwable> T queryForSingleInstance(OrderByClause clause, Class<E> exception, Object... params) throws E {
		return queryForSingleInstance(clause.toString(), exception, params);
	}

	public T queryForSingleInstance(SelectClause<T> clause, Object... params) {
		return queryForSingleInstance(clause.toString(), params);
	}

	public <E extends Throwable> T queryForSingleInstance(SelectClause<T> clause, Class<E> exception, Object... params) throws E {
		return queryForSingleInstance(clause.toString(), exception, params);
	}

	public T queryForSingleInstance(SubQuery<T> clause, Object... params) {
		return queryForSingleInstance(clause.toString(), params);
	}

	public <E extends Throwable> T queryForSingleInstance(SubQuery<T> clause, Class<E> exception, Object... params) throws E {
		return queryForSingleInstance(clause.toString(), exception, params);
	}

	public T queryForSingleInstance(String clause, Object... params) {
		List<T> list = query(clause, params);
		if (list.isEmpty() || list.size() > 1) {
			throw new IncorrectResultSizeDataAccessException(1, list.size());
		}
		return list.get(0);
	}

	public <E extends Throwable> T queryForSingleInstance(String clause, Class<E> exception, Object... params) throws E {
		List<T> list = query(clause, params);
		if (list.isEmpty() || list.size() > 1) {
			try {
				throw exception.newInstance();
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			} catch (InstantiationException e) {
				throw new RuntimeException(e);
			}
		}
		return list.get(0);
	}

	private static JapaEngine getJapaEngine() {
		JapaEngine engine = JapaEngine.LOCAL_JAPA_ENGINE.get();
		if (engine == null) {
			throw new IllegalStateException("Japa Engine not bound");
		}
		return engine;
	}

}
